/*
 *  Copyright 2004 Blandware (http://www.blandware.com)
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.blandware.atleap.webapp.taglib.core.menu.context;

import com.blandware.atleap.webapp.taglib.core.util.ContextMenuItem;
import com.blandware.atleap.webapp.util.core.WebappConstants;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts.taglib.TagUtils;

import javax.servlet.ServletException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.JspFragment;
import java.io.IOException;
import java.util.List;
import java.util.ListIterator;

/**
 * <p>This tag is used to prepare and render multi-level context menu.
 * Context menu (as rendered) consists of menu items,
 * each of them may be a submenu or 'end' item, clicking on which will cause some
 * URL to be invoked. Context menu support tags where developed to be used with
 * grids, so they allow to create such context menu, that its structure will be
 * similar for different grid rows, but right mouse button click on a row will
 * cause the appearance of context menu for that concrete row.
 * </p>
 * <p>
 * Whole context menu is marked with <em>contextMenu</em> tag. If you want to
 * build a context menu for each row (or element) in some structure, you will
 * probably use some iteration tag inside <em>contextMenu</em> tag (like
 * <em>atleap:rowsIterator</em>). Inside it, all context menu tags need to be
 * contained in <em>itemSet</em>. This is because tags need to know where
 * current set of items (the set that belongs to single data row) ends.
 * </p>
 * <p>
 * Inside <em>itemSet</em> tag, there can be <em>item</em> tags. Each of
 * them may render to 2 targets. First of them is context menu itself. Here,
 * <em>itemLink</em> will define the URL that corresponds to this context menu
 * item, <em>itemLabel</em> will generate a label that will be shown for that
 * context menu item. Second target is page on which context menu is rendered.
 * <em>itemLink</em> will produce a hyperlink, while <em>itemLabel</em>
 * will be ignored.
 * </p>
 * <p>
 * Each context menu item may be in one of following states:
 * <em>ebabled</em>, <em>disabled</em> and <em>hidden</em>. First two correspond
 * to item that has corresponding <b>enabled</b> attribute and
 * <code>includeInContextMenu="true"</code>. Hidden context menu item is such
 * that has <code>includeInContextMenu="false"</code>.
 * </p>
 * <p>
 * Enabled and disabled items has following distinctions:
 * <ul>
 * <li>
 * for enabled item, <em>itemEnabled</em> tag is rendered, for disabled item --
 * <em>itemDisabled</em> tag is used
 * </li>
 * <li>
 * different styles are used for DIVs that will represent menu items in context
 * menu: by default classes named 'contextMenuItem' and
 * 'highlightedContextMenuItem' for enabled items and 'disabledContextMenuItem'
 * and 'highlightedDisabledContextMenuItem' for disabled items are used
 * </li>
 * <li>
 * for disabled item, link for <em>itemLink</em> tag will not be generated when
 * rendering to page (but body of <em>itemLink</em> tag will be rendered)
 * </li>
 * </ul>
 * </p>
 * <p>
 * You are free to decide, how item must be rendered in enabled or disabled states.
 * </p>
 * <p>
 * Context menu items may be nested: menu item may actually be a submenu. For
 * example of nested menu, see package.html in this package.
 * </p>
 * <p>
 * Allowed attributes are:
 * <ul>
 * <li>
 * <b>tagId</b> - optional ID of this tag. If omitted, it will be generated
 * automatically and may be accessed via variable, specified in <b>var</b>
 * attribute.
 * </li>
 * <li>
 * <b>var</b> - name of variable that will store tagId of this tag. If not  specified,
 * <code>com.blandware.atleap.webapp.taglib.core.menu.context.DEFAULT_VAR_NAME</code>
 * will be used.
 * </li>
 * <li>
 * <b>scope</b> - scope of variable that will store tagId of this tag. Default
 * is "page".
 * </li>
 * <li>
 * <b>page</b> - context-relative path to JSP page used to render context menu
 * in a whole. Default is <code>WebappConstants.DEFAULT_CONTEXT_MENU_JSP</code>.
 * </li>
 * </ul>
 * </p>
 * <p>
 * For example on using this tag, see package.html in this package.
 * </p>
 * <p><a href="ContextMenuTag.java.html"><i>View Source</i></a></p>
 *
 * @author Sergey Zubtsovskiy <a href="mailto:sergey.zubtsovskiy@blandware.com">&lt;sergey.zubtsovskiy@blandware.com&gt;</a>
 * @version $Revision: 1.7 $ $Date: 2006/08/03 10:08:24 $
 * @see ItemSetTag
 * @see ItemTag
 * @jsp.tag name="contextMenu"
 * body-content="scriptless"
 */
public class ContextMenuTag extends ItemContainerSupport implements ItemContainer {

	// ~ Static fields

	/**
	 * Default variable name for exporting ID of this tag
	 */
	public static final String DEFAULT_VAR_NAME = "com.blandware.atleap.webapp.taglib.core.menu.context.ContextMenuTag.TAG_ID_VARIABLE";

	/**
	 * Context key, under which number of this tag is stored
	 */
	protected static final String NUMBER_KEY = "com.blandware.atleap.webapp.taglib.core.menu.context.ContextMenuTag.NUMBER";

	// ~ Instance fields

	/**
	 * Commons Logging instance for this class
	 */
	protected transient final Log log = LogFactory.getLog(getClass());

	/**
	 * Optional ID of this tag. If omitted it will be generated automatically
     * and may be accessed via variable, specified in <code>var</code> attribute.
	 */
	protected String tagId;

	/**
	 * Name of variable to export ID of this tag
	 */
	protected String var = DEFAULT_VAR_NAME;

	/**
	 * Scope to export tagId variable to. Default scope is "page"
	 */
	protected String scope = "page";

	/**
	 * Context-relative path to JSP page used to render context menu in a whole
	 */
	protected String page = WebappConstants.DEFAULT_CONTEXT_MENU_JSP;

	/**
	 * Creates new instance of ContextMenuTag
	 */
	public ContextMenuTag() {
	}

	/**
     * Returns ID of gererated tag
     *
	 * @return tag ID
     * @see #tagId
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Optional ID of this tag"
	 */
	public String getTagId() {
		return tagId;
	}

    /**
     * Sets ID of generated tag
     *
     * @param tagId tag ID to set
     * @see #tagId
     */
	public void setTagId(String tagId) {
		this.tagId = tagId;
	}

	/**
     * Returns name of variable that will accept ID of generated tag
     *
	 * @return variable name
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Name of variable to export tag ID"
	 */
	public String getVar() {
		return var;
	}

    /**
     * Sets name of variable that will accept ID of generated tag
     *
     * @param var variable name to set
     */
	public void setVar(String var) {
		this.var = var;
	}

	/**
     * Returns variable scope
     *
     * @return variable scope
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Scope to export tagId variable to"
	 */
	public String getScope() {
		return scope;
	}

    /**
     * Sets variable scope
     *
     * @param scope variable scope to set
     */
	public void setScope(String scope) {
		this.scope = scope;
	}

	/**
     * Returns path to JSP used to render context menu
     *
	 * @return path to JSP
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Context-relative path to JSP page used to render context menu in a whole"
	 */
	public String getPage() {
		return page;
	}

    /**
     * Sets path to JSP used to render context menu
     *
     * @param page path to JSP
     */
	public void setPage(String page) {
		this.page = page;
	}

	/**
	 * Performs tag logic
	 *
	 * @throws JspException
	 * @throws IOException
	 */
	public void doTag() throws JspException, IOException {
		PageContext pageContext = (PageContext) getJspContext();

		// set tag number
		Integer tagNumber = (Integer) pageContext.getAttribute(NUMBER_KEY, PageContext.REQUEST_SCOPE);
		if ( tagNumber == null ) {
			tagNumber = new Integer(0);
		} else {
			tagNumber = new Integer(tagNumber.intValue() + 1);
		}
		pageContext.setAttribute(NUMBER_KEY, tagNumber, PageContext.REQUEST_SCOPE);

		// generate ID if not specified
		if ( tagId == null || tagId.trim().length() == 0 ) {
			tagId = "__context__menu__" + tagNumber;
		}

		// ensure that var is not null
		if ( var == null ) {
			var = DEFAULT_VAR_NAME;
		}

		// export ID in specified scope
		int varScope = PageContext.PAGE_SCOPE;
		TagUtils tagUtils = TagUtils.getInstance();
		if ( scope != null ) {
			varScope = tagUtils.getScope(scope);
		}
		pageContext.setAttribute(var, tagId, varScope);

		// get body content and invoke it
		JspFragment body = getJspBody();
		if ( body != null ) {
			body.invoke(null);
		}

		// create context menus from lists of child items
		for ( ListIterator i = menus.listIterator(); i.hasNext(); ) {
			List row = (List) i.next();
			ContextMenuItem menu = new ContextMenuItem();
			menu.setId(tagId);
			// add items from row as child items of top menu
			for ( int j = 0; j < row.size(); j++ ) {
				ContextMenuItem item = (ContextMenuItem) row.get(j);
				menu.addChildItem(item);
			}
			i.set(menu);
		}

		// export created context menus to the request
		pageContext.setAttribute(WebappConstants.CONTEXT_MENUS_KEY, menus, PageContext.REQUEST_SCOPE);

		// include JSP
		try {
			pageContext.include(page);
		} catch ( ServletException e ) {
			throw new JspException(e);
		}
	}

}
